package chapter9;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

import java.util.Objects;

public class RedisLockUtil {
    /**
     * 尝试获取分布式锁
     *
     * @param jedis         Jedis实例，用于操作Redis
     * @param key           锁的键名，通常为加锁的资源标识
     * @param sourceId      请求加锁的唯一标识，如当前线程ID
     * @param expireSeconds 锁的超时时间，单位为秒
     * @return 返回true表示成功获取锁，返回false表示获取锁失败
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String key, String sourceId, int expireSeconds) {
        // 使用set命令的nx（仅当key不存在时设置）和ex（设置过期时间）选项来尝试获取分布式锁
        // 如果锁获取成功，返回"OK"，否则返回null
        // 注意：set nx和设置过期时间expire必须写到一行命令，否则无法保证两个操作的原子性，导致始终无法释放锁
        if (Objects.equals(jedis.set(key, sourceId, SetParams.setParams().nx().ex(expireSeconds)), "OK")) {
            System.out.println(sourceId + " get distributed lock");
            return true;
        }
        return false;
    }

    /**
     * 释放分布式锁
     *
     * @param jedis    Jedis实例，用于执行Redis操作
     * @param key      锁的键名，用于标识一个分布式锁
     * @param sourceId 锁的拥有者标识，用于确保只有锁的拥有者才能释放锁
     * @return boolean 表示锁释放的结果，true表示成功释放，false表示释放失败
     * <p>
     * 通过使用Lua脚本在Redis服务器端校验并释放锁，以保证操作的原子性
     * 脚本逻辑：检查锁的拥有者是否与传入的sourceId一致，如果一致则删除锁，返回1；否则返回0
     * 这样可以防止其他客户端误释放了不属于自己的锁
     */
    public static boolean releaseDistributedLock(Jedis jedis, String key, String sourceId) {
        // 定义Lua脚本，用于在Redis服务器端检查并释放分布式锁
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        // 执行Lua脚本，传入锁的键名和拥有者标识
        Object result = jedis.eval(script, 1, key, sourceId);
        // 检查脚本执行结果，如果删除成功，则打印信息并返回true
        if ("1".equals(result.toString())) {
            System.out.println(sourceId + " release distributed lock");
            return true;
        }
        // 删除失败，返回false
        return false;
    }

}
